/*
 * Copyright (c) 2008, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tansu_internal.h"
#include "tansu_status.h"
#include "tansu_util.h"
#include "tansu_http.h"

#if 0
struct tansu_keyval *
tansu_keyvals_get(const struct tansu_keyvals *head, const char *name);

struct tansu_keyval *
tansu_keyvals_get(const struct tansu_keyvals *head, const char *name)
{
    struct tansu_keyval *keyval;

    STAILQ_FOREACH(keyval, head, next) {
        if (strcasecmp(name, keyval->key) == 0)
            return keyval;
    }
    return NULL;
}
#endif

struct tansu_http_request_header *tansu_http_request_header_new()
{
    struct tansu_http_request_header *req;

    req = malloc(sizeof(struct tansu_http_request_header));
    if (req) {
        req->method = NULL;
        req->uri = NULL;
        req->version = NULL;
        STAILQ_INIT(&req->headers);
    }

    return req;
}

struct tansu_http_response_header *tansu_http_response_header_new()
{
    struct tansu_http_response_header *response;

    response = malloc(sizeof(struct tansu_http_response_header));
    if (response) {
        response->version = NULL;
        response->status = 500;
        response->readable = NULL;
        STAILQ_INIT(&response->headers);
    }

    return response;
}

tansu_status_t
tansu_http_request_header_set(struct tansu_http_request_header *req,
                              const char *method,
                              const char *uri,
                              const char *version)
{
    if (!(req && method && uri && version))
        return TANSU_INVALID_ARGUMENT;

    req->method = strdup(method);
    req->uri = strdup(uri);
    req->version = strdup(version);
    if (!(req->method && req->uri && req->version))
        goto out;

    return TANSU_SUCCESS;

out:
    free(req->method);
    free(req->uri);
    free(req->version);
    return TANSU_MEMORY_ERROR;
}

tansu_status_t
tansu_http_response_header_set(struct tansu_http_response_header *response,
                               const char *version,
                               int status,
                               const char *readable)
{
    if (!(response && version && readable))
        return TANSU_INVALID_ARGUMENT;

    response->version = strdup(version);
    response->readable = strdup(readable);

    if (!(response->version && response->readable))
        goto out;

    response->status = status;
    return TANSU_SUCCESS;

out:
    free(response->version);
    free(response->readable);
    return TANSU_MEMORY_ERROR;
}

tansu_status_t
tansu_http_header_add(struct tansu_keyvals *head,
                      const char *name,
                      const char *value)
{
    struct tansu_keyval *keyval;

    if (head == NULL || name == NULL || value == NULL)
        return TANSU_INVALID_ARGUMENT;

    keyval = tansu_keyvals_get(head, name);

    if (keyval) {

        char *newval = strdup(value);
        if (newval == NULL) {
            free(newval);
            return TANSU_MEMORY_ERROR;
        }

        free(keyval->value);
        keyval->value = newval;

    } else {

        keyval = malloc(sizeof(struct tansu_keyval));
        if (keyval == NULL)
            return TANSU_MEMORY_ERROR;

        keyval->key = strdup(name);
        keyval->value = strdup(value);
        if (keyval->key == NULL || keyval->value == NULL)
            goto out;

        STAILQ_INSERT_TAIL(head, keyval, next);
    }

    return TANSU_SUCCESS;

out:
    tansu_keyval_delete(keyval);
    return TANSU_MEMORY_ERROR;
}

tansu_status_t
tansu_http_header_remove(struct tansu_keyvals *head, const char *name)
{
    struct tansu_keyval *keyval;

    if (head == NULL || name == NULL)
        return TANSU_INVALID_ARGUMENT;

    keyval = tansu_keyvals_get(head, name);
    if (keyval == NULL)
        return TANSU_NOT_FOUND;

    STAILQ_REMOVE(head, keyval, tansu_keyval, next);
    tansu_keyval_delete(keyval);

    return TANSU_SUCCESS;
}

const char *
tansu_http_header_get(const struct tansu_keyvals *head, const char *name)
{
    struct tansu_keyval *keyval;

    if (head == NULL || name == NULL)
        return NULL;

    keyval = tansu_keyvals_get(head, name);
    if (keyval == NULL)
        return NULL;

    return keyval->value;
}

void
tansu_http_header_clear(struct tansu_keyvals *head)
{
    struct tansu_keyval *keyval;

    if (head == NULL)
        return;

    while ((keyval = STAILQ_FIRST(head))) {
        STAILQ_REMOVE_HEAD(head, next);
        tansu_keyval_delete(keyval);
    }
}

void
tansu_http_request_header_delete(struct tansu_http_request_header *req)
{
    if (req == NULL)
        return;

    tansu_http_header_clear(&req->headers);
    free(req->method);
    free(req->uri);
    free(req->version);
    free(req);
}

void
tansu_http_response_header_delete(struct tansu_http_response_header *response)
{
    if (response == NULL)
        return;

    tansu_http_header_clear(&response->headers);
    free(response->version);
    free(response->readable);
    free(response);
}

int
tansu_http_parse_lines(struct tansu_keyvals *headers, char **text)
{
    tansu_status_t ret;
    char *line;

    while ((line = tansu_buffer_readline(*text, text))) {
        char *name;
        char *value;
        if (line[0] == '\0')
            return 1;

        ret = tansu_http_parse_header_line(line, &name, &value);
        if (ret == TANSU_SUCCESS)
            if (tansu_http_header_add(headers, name, value) != TANSU_SUCCESS)
                return -1;
    }

    return 0;
}

struct tansu_http_request_header *
tansu_http_parse_request_header(char **text)
{
    char *line;
    char *method;
    char *uri;
    char *version;
    struct tansu_http_request_header *req;
    tansu_status_t ret;

    if (text == NULL || *text == NULL)
        return NULL;

    req = tansu_http_request_header_new();
    if (req == NULL)
        return NULL;

    line = tansu_buffer_readline(*text, text);
    ret = tansu_http_parse_request_line(line, &method, &uri, &version);
    if (ret != TANSU_SUCCESS)
        goto out;

    ret = tansu_http_request_header_set(req, method, uri, version);
    if (ret != TANSU_SUCCESS)
        goto out;

    if (tansu_http_parse_lines(&req->headers, text) == 1)
        return req;

out:
    tansu_http_request_header_delete(req);
    return NULL;
}

struct tansu_http_response_header *
tansu_http_parse_response_header(char **text)
{
    char *line;
    char *version;
    int status;
    char *readable;
    struct tansu_http_response_header *response;
    tansu_status_t ret;

    if (text == NULL || *text == NULL)
        return NULL;

    response = tansu_http_response_header_new();
    if (response == NULL)
        return NULL;

    line = tansu_buffer_readline(*text, text);
    ret = tansu_http_parse_response_line(line, &version, &status, &readable);
    if (ret != TANSU_SUCCESS)
        goto out;

    ret = tansu_http_response_header_set(response, version, status, readable);
    if (ret != TANSU_SUCCESS)
        goto out;

    if (tansu_http_parse_lines(&response->headers, text) == 1)
        return response;

out:
    tansu_http_response_header_delete(response);
    return NULL;
}

tansu_status_t
tansu_http_parse_request_line(char *line,
                              char **method,
                              char **uri,
                              char **version)
{
    char *f0;
    char *f1;
    char *f2;

    if (line == NULL)
        return TANSU_INVALID_ARGUMENT;

	f0 = strsep(&line, " \t");
	if (line == NULL)
		return TANSU_INVALID_FORMAT;

	f1 = strsep(&line, " \t");
	if (line == NULL)
		return TANSU_INVALID_FORMAT;

	f2 = strsep(&line, " \t");
	if (line != NULL)
		return TANSU_INVALID_FORMAT;

    TANSU_SET_STATUS(method, f0);
    TANSU_SET_STATUS(uri, f1);
    TANSU_SET_STATUS(version, f2);

    return TANSU_SUCCESS;
}

tansu_status_t
tansu_http_parse_response_line(char *line,
                               char **version,
                               int *status,
                               char **readable)
{
    char *f0;
    char *f1;
    int st;

    if (line == NULL)
        return TANSU_INVALID_ARGUMENT;

	f0 = strsep(&line, " \t");
	if (line == NULL)
		return TANSU_INVALID_FORMAT;

	f1 = strsep(&line, " \t");
	if (line == NULL)
		return TANSU_INVALID_FORMAT;

    st = strtol(f1, &f1, 10);
    if (f1 == NULL || *f1 != '\0')
		return TANSU_INVALID_FORMAT;

    TANSU_SET_STATUS(version, f0);
    TANSU_SET_STATUS(status, st);
    TANSU_SET_STATUS(readable, line);

    return TANSU_SUCCESS;
}

tansu_status_t
tansu_http_parse_header_line(char *line, char **name, char **value)
{
    char *key;

    if (line == NULL)
        return TANSU_INVALID_ARGUMENT;

    key = strsep(&line, ":");
    if (line == NULL)
        return TANSU_INVALID_FORMAT;

    line += strspn(line, " \t");

    TANSU_SET_STATUS(name, key);
    TANSU_SET_STATUS(value, line);

    return TANSU_SUCCESS;
}

